package com.xiaoshi.config;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.xiaoshi.interfaces.Constants;
import com.xiaoshi.oauth.AbstractCustomTokenGranter;
import com.xiaoshi.oauth.CustomTokenEnhancer;
import com.xiaoshi.oauth.impl.UserDetailLoginServiceImpl;
import com.xiaoshi.oauth.login.RedisClientDetailsServiceImpl;
import com.xiaoshi.translator.ResWebResponseExceptionTranslator;
import com.xiaoshi.utils.ReflectionsUtils;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;

/**
 * 认证服务器 相关配置
 */
@Configuration
@EnableAuthorizationServer  //开启认证服务器
public class XiaoshiAuthorizationServerConfigure extends AuthorizationServerConfigurerAdapter {

    private static ArrayList<TokenGranter> granter = new ArrayList();


    /**
     * 会出现循环依赖的问题  问题未知   把注解换成 @Resource
     * 如果已经是 @Resource   把注解换成 @Autowired
     */
    @Resource
    private AuthenticationManager authenticationManager;
    //    @Autowired
//    private RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private UserDetailLoginServiceImpl userDetailService;


//    @Autowired
//    private PasswordEncoder passwordEncoder;

    /**
     * 异常翻译器
     */
    @Autowired
    private ResWebResponseExceptionTranslator exceptionTranslator;

//    @Autowired
//    private DataSource dataSource;


    /**
     * 多数据原的时候  使用
     */
    @Autowired
    private DynamicRoutingDataSource dynamicRoutingDataSource;


    @Autowired
    private CustomTokenEnhancer customTokenEnhancer;


//    private final RedisAuthenticationCodeService authenticationCodeService;

    @Autowired
    private RedisClientDetailsServiceImpl redisClientDetailsService;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;


    /**
     * 客户端从认证服务器获取令牌
     * 该client_id支持password模式获取令牌，并且可以通过refresh_token来获取新的令牌；
     * 在获取client_id为小事的令牌的时候，scope只能指定为all，否则将获取失败；
     * 如果需要指定多个client，可以继续使用withClient配置。
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //    clients.withClientDetails(jdbcClientDetailsService());  // 数据库
        clients.withClientDetails(redisClientDetailsService);
    }


    /**
     * 登入时  如果不配置 Authorization    Basic eGlhb3NoaToxMjM0NTY=  请打开注释
     * eGlhb3NoaToxMjM0NTY=  是client_id:client_secret  加密得到的
     *
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)  {

        security//客户端校验token访问许可 /oauth/check_key公开
                .checkTokenAccess("permitAll()")
                //客户端token调用许可 /oauth/check_token公开  // 获取密钥需要身份认证，使用单点登录时必须配置
                .tokenKeyAccess("permitAll()")
                //表单认证,申请令牌
                .allowFormAuthenticationForClients();

    }
    @Override
    @SuppressWarnings("all")
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {


        endpoints.tokenStore(tokenStore())
                .userDetailsService(userDetailService)
                .authenticationManager(authenticationManager) //身份认证管理器
                .tokenServices(defaultTokenServices())
                .exceptionTranslator(exceptionTranslator)
        ;


        /**
         * 其他  登入方法
         */
        List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
        tokenGranters.add(endpoints.getTokenGranter());
        endpoints.exceptionTranslator(new ResWebResponseExceptionTranslator());
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));


    }


    /**
     * 其他  登入方法  请写在outh2 .login 包下
     *
     * @param tokenServices
     * @param clientDetailsService
     * @param requestFactory
     * @return
     */
    @SneakyThrows
    private List<TokenGranter> getTokenGranters(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {

        Set<Class<? extends AbstractCustomTokenGranter>> subTypes = ReflectionsUtils.reflections.getSubTypesOf(AbstractCustomTokenGranter.class);
        for (Class<? extends AbstractCustomTokenGranter> subType : subTypes) {
            Constructor<? extends AbstractCustomTokenGranter> constructor = subType.getConstructor(AuthorizationServerTokenServices.class, ClientDetailsService.class, OAuth2RequestFactory.class, UserDetailLoginServiceImpl.class);
            AbstractCustomTokenGranter abstractCustomTokenGranter = constructor.newInstance(tokenServices, clientDetailsService, requestFactory, userDetailService);
            granter.add(abstractCustomTokenGranter);
        }

        /**
         * 客户端登入
         */
        ClientCredentialsTokenGranter clientCredentialsTokenGranter = new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory);
        // 设置返回refresh code
        clientCredentialsTokenGranter.setAllowRefresh(true);
        granter.add(clientCredentialsTokenGranter);
        return granter;

//        return new ArrayList<>(Arrays.asList(
//                /** todo 新增的grant_type 添加到这里*/
//                new WebPhoneLoginTokenGranter(tokenServices,clientDetailsService,requestFactory,userDetailService),
//                new WebPhoneLoginTokenGranter(tokenServices,clientDetailsService,requestFactory,userDetailService),
//                new WebDeaultPcLoginGranter(tokenServices,clientDetailsService,requestFactory,userDetailService)
//
//        ));
    }


    /**
     * 数据库 存储
     *
     * @return
     */
    public DataSource getDataSource() {
        return dynamicRoutingDataSource.getDataSource(Constants.dataSourceName.DATA_SOURCE_NAME_ONE);
    }


    /**
     * 数据库 存储   clients
     *
     * @return
     */
    @Bean
    public ClientDetailsService jdbcClientDetailsService() {

        return new JdbcClientDetailsService(getDataSource());


    }


    /**
     * 令牌的存储方式  数据库
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        //DataSource dataSource = dynamicRoutingDataSource.getDataSource("xiaoshi_resources");
        //  return new JdbcTokenStore(getDataSource());


        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        // 解决每次生成的 token都一样的问题
        redisTokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
        return redisTokenStore;
    }

    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore()); //使用的是数据库，认证服务器生成的令牌将被存储到数据库中

        //令牌增强
        // 如果已经生成了一次没有自定义的token信息，需要去redis里删除掉该token才能再次测试结果，不然你的结果一直是错误的，因为token还没过期的话，是不会重新生成的。
        tokenServices.setTokenEnhancer(this.customTokenEnhancer);
        tokenServices.setSupportRefreshToken(true);  //开启刷新令牌的支持。

        //tokenServices.setClientDetailsService(jdbcClientDetailsService());
        tokenServices.setClientDetailsService(redisClientDetailsService);

//        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24);  //令牌有效时间
//        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7); //，刷新令牌有效时间

        return tokenServices;
    }


    @Bean
    public DefaultOAuth2RequestFactory oAuth2RequestFactory() {
        return new DefaultOAuth2RequestFactory(redisClientDetailsService);
    }


}
